package org.c41.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Semaphore;

public class SynIOStream{

	private LinkedBlockingDeque<Byte> queue;
	private Semaphore write_s = new Semaphore(1);
	private Semaphore read_s = new Semaphore(1);
	private OutputStream out =new Out();
	private InputStream in = new In();
	private boolean block = true;

	public SynIOStream(){
		this.queue = new LinkedBlockingDeque<>();
	}

	public SynIOStream(int buffsize){
		this.queue=new LinkedBlockingDeque<>(buffsize);
	}

	public void write(int b) throws IOException{
		try {
			write_s.acquire();
			try{
				m_write((byte)b);
			}finally{
				write_s.release();
			}
		} catch (InterruptedException e) {
			throw new IOException(e);
		}
	}

	private void m_write(byte b) throws InterruptedException{
		queue.put(b);
	}

	public void write(byte[] bb) throws IOException{
		try {
			write_s.acquire();
			try{
				for(byte b:bb){
					m_write(b);
				}
			}finally{
				write_s.release();
			}
		} catch (InterruptedException e) {
			throw new IOException(e);
		}
	}

	public void write(byte[] bb, int off, int len) throws IOException{
		try {
			write_s.acquire();
			try{
				for(int i=off;i<off+len;i++){
					m_write(bb[i]);
				}
			}finally{
				write_s.release();
			}
		} catch (InterruptedException e) {
			throw new IOException(e);
		}
	}

	public int read() throws IOException{
		try {
			read_s.acquire();
			try{
				if(block || queue.peekFirst()!=null){
					return queue.take();
				}
				else{
					return -1;
				}
				
			}finally{
				read_s.release();
			}
		} catch (InterruptedException e) {
			throw new IOException(e);
		}
	}

	public int read(byte[] bb) throws IOException{
		return read(bb,0,bb.length);
	}

	public int read(byte[] bb, int off, int length) throws IOException{
		try {
			read_s.acquire();
			try{
				if(!block&&this.queue.peekFirst()==null){
					return -1;
				}
				int n_read = 0;
				bb[off] = this.queue.take();
				n_read++;
				for(int i=off+1;i<bb.length+length;i++){
					if(this.queue.peekFirst()!=null){
						bb[i]=this.queue.take();
						n_read++;
					}
					else{
						break;
					}
				}
				return n_read;
			}finally{
				read_s.release();
			}
		} catch (InterruptedException e) {
			throw new IOException(e);
		}
	}

	public void setBlocked(boolean b){
		this.block=b;
	}
	
	public boolean isBlocked(){
		return this.block;
	}
	
	public int available(){
		return this.queue.size();
	}

	public long skip(long n) throws IOException{
		long result=0;
		try {
			read_s.acquire();
			try{
				for(long i=0;i<n;i++){
					read();
					result++;
				}
				return result;
			}finally{
				read_s.release();
			}
		} catch (InterruptedException e) {
			return result;
		}
	}
	
	public OutputStream getOutputStream(){
		return out;
	}

	public InputStream getInputStream(){
		return in;
	}

	private class Out extends OutputStream{

		@Override
		public void close() {}

		@Override
		public void flush() {}

		@Override
		public void write(byte[] bb) throws IOException {
			SynIOStream.this.write(bb);
		}

		@Override
		public void write(byte[] b, int off, int len) throws IOException {
			SynIOStream.this.write(b,off,len);
		}

		@Override
		public void write(int b) throws IOException {
			SynIOStream.this.write(b);
		}

	}

	private class In extends InputStream{

		@Override
		public int available() {
			return SynIOStream.this.available();
		}

		@Override
		public void close() {}

		@Override
		public void mark(int readlimit) {
		}

		@Override
		public boolean markSupported() {
			return false;
		}

		@Override
		public int read() throws IOException {
			return SynIOStream.this.read();
		}

		@Override
		public int read(byte[] b) throws IOException {
			return SynIOStream.this.read(b);
		}

		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			return SynIOStream.this.read(b,off,len);
		}

		@Override
		public void reset() {}

		@Override
		public long skip(long n) throws IOException  {
			return SynIOStream.this.skip(n);
		}
	}

}
